package modules

import (
	"fastdp-ops/cmd/agent/zaplog"
	"fastdp-ops/pkg/agent/utils"
	pb "fastdp-ops/proto/exec"
	"fmt"
	"go.uber.org/zap"
	"io"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"
)

// 定义错误码常量
const (
	ErrorCodeInvalidParams = 500
)

type FilePullModule struct{}

func NewFilePullModule() Module {
	return &FilePullModule{}
}

func init() {
	Register("file_pull", NewFilePullModule) // 模块名改为 file_pull 更准确
}

func (m *FilePullModule) Run(req *pb.Execrequest) (*pb.ExecResponse, error) {
	// 1. 参数校验
	url, urlok := req.Parameters["url"]
	md5sum, md5sumok := req.Parameters["md5"]
	dest, destok := req.Parameters["dest"]
	destType := req.Parameters["type"]

	// 检查必填参数
	if !urlok || !md5sumok {
		return utils.ErrorResponse(req, ErrorCodeInvalidParams, "未传入必须参数: url 或 md5"), nil
	}

	// 检查类型参数
	if destType != "file" && destType != "dir" {
		return utils.ErrorResponse(req, ErrorCodeInvalidParams, "参数type必须为'file'或'dir'"), nil
	}

	// 2. 确定目标路径
	var finalDest string
	switch destType {
	case "file":
		if !destok || !filepath.IsAbs(dest) {
			return utils.ErrorResponse(req, ErrorCodeInvalidParams,
				fmt.Sprintf("目标文件位置dest必须输入且需绝对路径，当前输入为:%s", dest)), nil
		}

		// 检查文件状态
		if f, err := os.Stat(dest); err == nil && f.IsDir() {
			return utils.ErrorResponse(req, ErrorCodeInvalidParams,
				fmt.Sprintf("目标位置为目录，请手动处理:%s", dest)), nil
		}
		finalDest = dest

	case "dir":
		if !destok {
			return utils.ErrorResponse(req, ErrorCodeInvalidParams, "未传入目标目录参数: dest"), nil
		}

		// 从URL提取文件名
		file_name := path.Base(strings.TrimRight(url, "/"))
		finalDest = filepath.Join(dest, file_name)
	}
	if _, err := os.Stat(finalDest); err == nil {
		// 文件已存在，计算当前MD5
		currentMd5, err := utils.FileMD5(finalDest)
		if err != nil {
			zaplog.Logger.Warn("文件存在但计算MD5失败，将重新拉取",
				zap.String("dest", finalDest),
				zap.Error(err))
		} else if currentMd5 == md5sum {
			// MD5匹配，无需拉取，返回change=false
			zaplog.Logger.Info("文件已存在且MD5匹配，无需拉取",
				zap.String("dest", finalDest),
				zap.String("md5", currentMd5))
			return utils.SuccessResponseWithNoChange(req,
				fmt.Sprintf("文件已存在且MD5匹配，无需拉取: %s", finalDest),
				"文件未变更"), nil
		} else {
			// MD5不匹配，需要重新拉取（记录日志）
			zaplog.Logger.Info("文件已存在但MD5不匹配，将重新拉取",
				zap.String("dest", finalDest),
				zap.String("expected_md5", md5sum),
				zap.String("current_md5", currentMd5))
		}
	} else if !os.IsNotExist(err) {
		// 非“文件不存在”的错误（如权限问题），记录日志但继续拉取
		zaplog.Logger.Warn("检查文件状态异常，将继续拉取",
			zap.String("dest", finalDest),
			zap.Error(err))
	}

	// 3. 执行文件拉取
	return pullFile(url, finalDest, md5sum, req), nil
}

func pullFile(url, dest, md5sum string, req *pb.Execrequest) *pb.ExecResponse {
	// 1. 下载文件
	client := &http.Client{
		Timeout: 60 * time.Second, // 超时时间（根据需求调整）
	}
	resp, err := client.Get(url) // 用带超时的客户端发送请求
	if err != nil {
		return utils.ErrorResponse(req, ErrorCodeInvalidParams,
			fmt.Sprintf("文件拉取异常: %s", err.Error()))
	}
	defer resp.Body.Close()

	// 检查HTTP状态
	if resp.StatusCode != http.StatusOK {
		return utils.ErrorResponse(req, ErrorCodeInvalidParams,
			fmt.Sprintf("拉取文件失败，状态码: %s", resp.Status))
	}

	// 2. 创建目标目录
	if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
		return utils.ErrorResponse(req, ErrorCodeInvalidParams,
			fmt.Sprintf("创建目录失败: %s", err.Error()))
	}

	// 3. 创建目标文件
	outFile, err := os.Create(dest)
	if err != nil {
		return utils.ErrorResponse(req, ErrorCodeInvalidParams,
			fmt.Sprintf("创建文件失败: %s", err.Error()))
	}
	defer outFile.Close()

	// 4. 写入文件内容
	if _, err := io.Copy(outFile, resp.Body); err != nil {
		return utils.ErrorResponse(req, ErrorCodeInvalidParams,
			fmt.Sprintf("写入文件失败: %s", err.Error()))
	}

	// 5. 校验MD5
	destMd5, err := utils.FileMD5(dest)
	if err != nil {
		return utils.ErrorResponse(req, ErrorCodeInvalidParams,
			fmt.Sprintf("计算文件md5失败:%s", err.Error()))
	}

	if md5sum != destMd5 {
		return utils.ErrorResponse(req, ErrorCodeInvalidParams,
			fmt.Sprintf("md5值不匹配: 期望 %s, 实际 %s", md5sum, destMd5))
	}

	// 6. 返回成功
	successMsg := fmt.Sprintf("文件%s已拉取至%s, md5值：%s", url, dest, destMd5)
	zaplog.Logger.Info("文件拉取成功", zap.String("url", url), zap.String("md5sum", destMd5), zap.String("dest", dest))
	return utils.SuccessResponse(req, successMsg, "文件拉取成功")

}
